package scan

import (
	"bytes"
	"crypto/md5"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"regexp"
	"strconv"
	"strings"

	"github.com/axgle/pinyin" // 良心
	"github.com/bitly/go-simplejson"
	"github.com/garyburd/redigo/redis"
	"github.com/lunny/xorm"

	"sora/data"
)

func ScanBook(c redis.Conn, bid string, body io.Reader) error {
	e, err := xorm.NewEngine("mysql", "sora:harunosora@/sora?charset=utf8")
	if err != nil {
		log.Fatal(err)
	}
	defer e.Close()

	// book check
	bpath := "sora:" + bid
	var bb bytes.Buffer
	bb.ReadFrom(body)
	if bb.Len() < 10 {
		return errors.New(bid + ":empty")
	}

	// 干掉换行符
	re := regexp.MustCompile("\\n")
	js, err := simplejson.NewJson(re.ReplaceAll(bb.Bytes(), []byte("")))
	if err != nil {
		return err
	}
	vs := js.MustArray() // []interface{}
	bcode := checkMD5(c, bpath, bb.Bytes())
	switch bcode {
	case 0: // 无变化
		fmt.Println(bid, ":书籍已存在")
		return nil
	case 1: // 一一检查vmd5
		// 从第一段json中取出book信息
		if err := loadBook(js.GetIndex(0)).Update(e); err != nil {
			return err
		}
		fmt.Println(bid, ":书籍有改动")
		i := 0 // 迭代计数
		for _, v := range vs {
			buf, _ := js.GetIndex(i).MarshalJSON()
			i++
			vpath := bpath + ":" + getVid(v)

			// 判断分卷md5
			vcode := checkMD5(c, vpath, buf)
			switch vcode {
			case 0: // volume no change
			case 2: // 分卷入库
				c.Send("SADD", bpath+":vset", getVid(v))
				fmt.Print("!")
				fallthrough
			case 1: // 分卷修改
				fmt.Println(bpath + ":" + getVid(v) + "改动！")
				err := setVolume2Redis(c, vpath, v)
				if err != nil {
					log.Fatal(err)
				}
				c.Send("SADD", "sora:qvset", vpath)
				c.Send("SADD", "sora:qzip", getVid(v))
			}
		}

	case 2: // 不用检查vmd5，直接set
		// 从第一段json中取出book信息
		if err := loadBook(js.GetIndex(0)).Insert(e); err != nil {
			return err
		}
		i := 0 // 迭代计数
		for _, v := range vs {
			buf, _ := js.GetIndex(i).MarshalJSON()
			i++
			vpath := bpath + ":" + getVid(v)
			checkMD5(c, vpath, buf) // 只为存储md5
			if err := setVolume2Redis(c, vpath, v); err != nil {
				fmt.Println(bid, ":", err)
			}
			c.Send("SADD", bpath+":vset", getVid(v))
			c.Send("SADD", "sora:qvset", vpath)
			c.Send("SADD", "sora:qzip", getVid(v))
		}
	}
	c.Flush()
	if _, err := c.Receive(); err != nil {
		return err
	}
	return nil
}

func loadBook(j *simplejson.Json) *data.Book {
	b := &data.Book{}
	id, _ := j.Get("bookid").String()
	b.Bid, _ = strconv.Atoi(id)
	b.Name, _ = j.Get("title").String()
	b.Initial = initial(b.Name)
	// 这里要经过处理
	imgurl, _ := j.Get("imageurl").String()
	h := md5.New()
	h.Write([]byte(imgurl))
	md := fmt.Sprintf("%x", h.Sum(nil))
	vid, _ := j.Get("volumeid").String()
	b.Cover = "/" + vid + "/img/" + md + ".jpg"
	// end
	b.Author, _ = j.Get("author").String()
	b.Press, _ = j.Get("press").String()
	b.Brief, _ = j.Get("introduction").String()
	b.Chick = 0
	b.Status = 0
	return b
}

func getVid(v interface{}) string {
	return v.(map[string]interface{})["volumeid"].(string)
}

// check & store md5
func checkMD5(c redis.Conn, path string, input []byte) int {
	h := md5.New()
	h.Write(input)
	md := fmt.Sprintf("%x", h.Sum(nil))
	if md5db, _ := redis.String(c.Do("HGET", path, "md5")); md5db != "" {
		if md == md5db {
			return 0
		} else { // update
			c.Do("HSET", path, "md5", md)
			return 1
		}
	} else { // insert
		c.Do("HSET", path, "md5", md)
		return 2
	}
}

// 当book的md5已经不同时（在此前新的book信息已经update）
func setVolume2Redis(c redis.Conn, path string, data interface{}) error {
	m := data.(map[string]interface{}) // 每章的json内容
	// 遍历获取每个kv
	for k, v := range m {
		switch k {
		case "index": // meet index field
			index, header := transIndex(v.(string))
			c.Send("HSET", path, strings.Title(k), index)
			c.Send("HSET", path, "Header", header)
		case "chapters": // meet chapters field
			// 遍历[]interface{}
			for _, item := range v.([]interface{}) {
				tv := item.(map[string]interface{})
				// 取map的内容
				for kv, vv := range tv {
					cid := tv["ChapterId"].(json.Number).String()
					switch vv.(type) { // value可能是json.Number或者string
					case string:
						c.Send("HSET", path+":"+cid, strings.Title(kv), vv.(string))
					case json.Number:
						n, _ := vv.(json.Number).Int64()
						c.Send("HSET", path+":"+cid, strings.Title(kv), n)
					default:
						return errors.New("chapter插入了未知类型")
					}
					// 在v级中存c的集合
					c.Send("SADD", path+":cset", cid)
				}
			}
		case "imageurl":
			vid := m["volumeid"].(string)
			imgurl := v.(string)
			h := md5.New()
			h.Write([]byte(imgurl))
			md := fmt.Sprintf("%x", h.Sum(nil))
			c.Send("HSET", path, strings.Title(k), "/"+vid+"/img/"+md+".jpg")
			// 加入爬图队列
			c.Send("ZADD", "sora:qimg", vid, imgurl)
		default: // others field
			c.Send("HSET", path, strings.Title(k), v.(string))
		}
	}
	c.Flush()
	_, err := c.Receive()
	if err != nil {
		fmt.Println(err)
	}
	return nil
}

// return index and header
func transIndex(in string) (int, string) {
	// 开始正则解析
	if result := regexp.MustCompile("^\\d+(\\.\\d+)?$").MatchString(in); result {
		// 如果全是数字，则对原数字乘10
		num, err := strconv.ParseFloat(in, 64)
		if err != nil {
			log.Fatal(err)
		}
		return int(10 * num), "第" + in + "卷"
	} else if result := regexp.MustCompile("\\d+(\\.\\d+)?").FindString(in); result != "" {
		// 如果是字符数字混合，则200+10x
		num, err := strconv.ParseFloat(result, 64)
		if err != nil {
			log.Fatal(err)
		}
		return int(200 + 10*num), in
	} else {
		// 全字符，400+10x
		return 400, in
	}
}

// 获取首字母
func initial(title string) string {
	return strings.ToUpper(string(pinyin.Convert(title)[0]))
}
